<!--
参考链接  https://www.h3399.cn/201805/579447.html
-->


<!DOCTYPE html>
<HTML lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>
        大文件的分片上传
    </title>
</head>

<body>
<input type="file" class="file">
<button type="button" class="btn" onclick="upload()">
    分片上传
</button>

<script src="./md5.js"></script>
<script type="text/javascript">

    // 上传点击事件
    function upload() {

        // 获取file文件临时地址
        var tag = document.querySelector('input[type="file"]');
        var file = tag.files[0];
        if(typeof file == "undefined"){
            alert('请先选择文件')
        }

        // 获取散列值，用于区分不同的文件 ，如果需要的话
        // 因为读取文件计算散列值是异步的，所以，需要在回调中进行其他步骤
        md5File(file).then(function (fileUniCode) {
            uploadFile(file,fileUniCode)
        })

    }

    // 计算文件 散列值 , 分片计算，避免内存过大
    function md5File(file) {
        return new Promise(function(resolve, reject) {
            //兼容方式获取slice方法
            var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;

            //每片大小
            var chunkSize = 1024*1024*100;
            //总分片数,当前分片
            var chunks = Math.ceil(file.size / chunkSize);
            var currentChunk = 0;

            // 实例化 md5 对象
            var spark = new SparkMD5.ArrayBuffer();
            let fileReader = new FileReader()

            // 读取文件事件回调 ,
            // fileReader 一次只能读取一个分片文件，所以，需要在读取结束之后才能进行下一次读取
            fileReader.onload = function (e) {
                // Append array buffer
                spark.append(e.target.result);
                currentChunk++
                if (currentChunk < chunks) {
                    read();
                } else {
                    var fileUniCode = spark.end();
                    resolve(fileUniCode);
                }
            };

            function read () {
                var start = currentChunk * chunkSize;
                var end   =   start + chunkSize;
                    end   =   (end >= file.size) ? file.size : end;
                fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
            }
            read()
        })

    }

    // 上传文件操作
    function uploadFile(file,fileUniCode = '') {

        // 每片的大小 ,总片数
        var blockSize = 1024 * 1024 * 10;
        var blockNum = Math.ceil(file.size / blockSize);
        // 如果需要的话，查询该文件从哪一部分开始上传
        var currentBlock = getCurrentIndex(fileUniCode);
        // 已上传完成片数
        var finishNum = 0;
        // 是否有错误
        var is_err = false;

        console.log(file.size,blockNum,blockSize)

        while (true)
        {
            if(is_err){
                break;
            }
            // 分片读取的开始、结束位置
            var startSize = currentBlock * blockSize;
            var endSize   = startSize + blockSize;
            console.log('s  e  ',startSize,endSize)
            if(startSize > file.size){
                break;
            }

            var fileData = file.slice(startSize,endSize ); // 截取 部分文件 块
            var formData = new FormData(); // 初始化一个 FormData 对象
            formData.append('file', fileData); // 将 部分文件 塞入 FormData
            formData.append('fileName', file.name); // 保存文件名字
            formData.append('fileUniCode', fileUniCode); // 保存文件名字
            formData.append('blockSize', blockSize); // 每块的大小
            formData.append('currentIndex', currentBlock); // 当前块

            // 特别注意，这里的请求是同步请求，是因为在使用异步请求测试时候，总是出现多发请求的情况，故改为同步请求，感兴趣的话可以自己试一下
            _post('index.php',formData,false,function (res) {
                // 回调函数
                finishNum++ ;
                console.log(finishNum,blockNum,res)
                // 当全部上传完成时，触发合并视频请求
                if(finishNum >= blockNum){
                    console.log('merge',res)
                    mergeFile(fileUniCode)
                }
            })
            currentBlock++;
        }

    }

    // 断连后，获取最后的上传节点
    function getCurrentIndex(fileUniCode = '') {
        var lastIndex = 0;
        if(fileUniCode){
            var formData = new FormData(); // 初始化一个 FormData 对象
            formData.append('fileUniCode', fileUniCode); // 保存文件名字
            formData.append('type', 'select'); // 每块的大小
            _post('index.php',formData,false,function (res) {
                lastIndex = res.lastIndex;
            })
        }

        return lastIndex;
    }

    function mergeFile(fileUniCode) {
        var formData = new FormData(); // 初始化一个 FormData 对象

        formData.append('fileUniCode', fileUniCode); // 保存文件名字
        formData.append('type', 'merge'); // 请求类型
        formData.append('suffix', 'mp4'); // 保存文件后缀
        _post('index.php',formData,true)
    }

    /**
     * post 请求
     * @param url 请求地址
     * @param formData 请求参数
     * @param async 是否异步
     * @param cf  callback function
     * @private
     */
    function _post(url='index.php',formData={},async = true , cf=function (res) { return res;}) {

        var xmlHttp = new XMLHttpRequest();

        xmlHttp.open('POST', url, async);
        // 回调函数
        xmlHttp.onreadystatechange = function(a) {
            var xmlHttp = a.currentTarget.response;
            var json = JSON.parse(xmlHttp);
            cf(json);
        };
        xmlHttp.send(formData);
    }


</script>
</body>

</HTML>
